<?php

/**
 * 路由类
 */
class router
{

    private $router_array = array();
    private $router_parsed = array();


    public static function Run()
    {
        $router = router::singleton();
        return $router->app_run();
    }

    public static function singleton()
    {
        $r = getGlobals('MainRouter');
        if ($r) {
            return $r;
        } else {
            $router = new router();
            setGlobals('MainRouter', $router);
            return $router;
        }
    }

    private function app_run()
    {
        global $AccessControlAllowOrigin;

        if ($AccessControlAllowOrigin) {
            allowOrigin($AccessControlAllowOrigin);
        }

        //解析路由，合并三个地方定义的路由规则
        $this->router_merge();


//        dump_pre($this->router_parsed);
        $uri = getRequestUri();
//        dump_pre($uri);


        //支持A /index.php/blog
        $noarguri = explode('?', $uri);
        $noarguri = $noarguri[0];
        if (str_cut($noarguri, strlen('/' . IndexFile)) == '/' . IndexFile) {
            $noarguri = substr($noarguri, strlen('/' . IndexFile));
        }
        $uri = $noarguri;


        //支持B /?router=/blog/cc
        $routerpath = trim(getGet('router'), "\/\t\n\r\0\x0B");
        if ($routerpath) {
            $uri = trim($uri, "\/\t\n\r\0\x0B");
            $uri .= '/' . $routerpath;
        }

//        exit($uri);
        if (RouterDebug) {
            echo("<!-- uri:$uri -->\r\n");
        }
        if (!is_array($this->router_parsed)) {
            return false;
        }

//        dump_pre($this->router_parsed);

        $request_method = request_method();
//dump_pre($request_method);
//        dump_pre($this->router_parsed);
        foreach ($this->router_parsed as $router_index => $router) {
            $ifmatch = true;
            if (RouterDebug) {
                echo("<!-- Router: " . str_replace("\\/", "/", json_encode($router)) . "-->\r\n");
            }

            if (isset($router['domain'])) {
//                dump_pre($router['domain']);
                $ifmatch = $this->macthdomain($router['domain']);
//                dump_pre($ifmatch);
            }
            if (!$ifmatch) continue;

            $ifmethod = false;
            //匹配访问方式
            !isset($router['method']) && $router['method'] = 'all';
            if ($request_method == $router['method'] || $router['method'] == 'all') {
                $ifmethod = true;
            }
            if (!$ifmethod) continue;

            !isset($router['access']) and $router['access'] = '';
            if ($router['access'] && $ifmatch) {
                //跨域请求匹配成功
                allowOrigin($router['access']);
            }

            $router_uri_arr = explode(';', $router['uri']);
            if (!is_array($router_uri_arr)) {
                $router_uri_arr = [$router['uri']];
            }
            foreach ($router_uri_arr as $router_uri) {
                $ifmatch = $this->matchuri($router_uri, $uri);
                if ($ifmethod && $ifmatch) {
                    if (isset($router['cid'])) {
                        if (!$this->routercid($router['cid'])) {
                            return false;
                        }
                    }
                    return $this->route_exec($router_index);
                }
            }
        }
        return false;
    }

    private function router_merge()
    {
        if (!DEBUG) {
            $all_router = cache_site_get('all_router', PageCache);
            if ($all_router) {
                $this->router_parsed = $all_router;
//                echo "!!!!";
//                dump_pre($this->router_parsed);
                return $all_router;
            }
        }

        //处理数据库中的路由（后台设定路由和页面设定路由）
        $router = cache_site_get('router', 30, 'str');
//        dump_pre($router);
        if ($router) {
            $ret = json_decode($router, 1);
        } else {
            $routerquery = M()->one("SELECT strvalue FROM " . tableex('str') . " where strname='router' and inputkind=0 limit 1;");
            if ($routerquery && !empty($routerquery['strvalue'])) {
                if (!DEBUG) {
                    cache_site_set('router', $routerquery['strvalue'], 30, 'str');
                }
                $ret = json_decode($routerquery['strvalue'], 1);
//                dump_pre($ret);
            } else {
                $ret = array(array('access' => '', 'method' => 'all', 'uri' => '/', 'res' => 'index', 'domain' => SystemDomain, 'cache' => PageCache, 'cid' => 0));
            }
        }

//        dump_pre($ret);
        $o_router = array();

        foreach ($this->router_array as $item) {
            !isset($item['method']) && $item['method'] = 'all';
            !isset($item['access']) && $item['access'] = '';
            !isset($item['cache']) && $item['cache'] = PageCache;
            $o_router[] = array('method' => $item['method'], 'access' => $item['access'], 'uri' => $item['uri'], 'res' => $item['controller'], 'domain' => SystemDomain, 'cache' => $item['cache'], 'cid' => 0, 'middleware' => $item['middleware']);
        }
        //存储到已编译路由中
        $this->router_parsed = array_merge($o_router, $ret);


//        dump_pre($ret);
        if (!DEBUG) {
            cache_site_set('all_router', $this->router_parsed, PageCache);
        }
        $ret = [];
        foreach ($this->router_parsed as $item) {
            if ($item['domain']) {
                array_unshift($ret, $item);
            } else {
                $ret[] = $item;
            }
        }
        $this->router_parsed = $ret;
//        dump_pre($this->router_parsed);

        return $this->router_parsed;
    }

    private function macthdomain($domains)
    {
//        echo $domains;
        if (!is_array($domains)) {
            if (empty($domains)) {
                return true;
            }
            $domains = explode(';', strtolower($domains));
        }
        $sn = server_name();
//        dump_pre($sn);
        foreach ($domains as $val) {
            $val = strtolower(trim($val));
            if ($val == $sn) {
                return true;
            }
            if ($val == '*') {
                return true;
            }
            $thisstripos = stripos($val, '*');
            if ($thisstripos !== false) {
                $topdomain = substr($val, $thisstripos + 1, strlen($val) - $thisstripos);
                if (substr($sn, -strlen($topdomain)) == $topdomain) {
                    return true;
                }
            }
        }
//        dump_pre($domains);
//        dump_pre($sn);
        return false;
    }

    private function matchuri($preguri, $uri)
    {
        $preguri = urlencode($preguri);
        $preguri = safe_uri_decode($preguri);

        $preguri = rtrim($preguri, '/');
        $uri = rtrim($uri, '/');
        if ($preguri == $uri && strpos($preguri, '(') === false && strpos($preguri, '[') === false && strpos($preguri, '{') === false) {
            if (RouterDebug) {
                echo("<!--Static Matched-Router: $preguri-->\r\n");
            }
            return true;
        }

//  echo "url:".$preguri;
        preg_match_all('/[{|\[|(](.*)[}|\]|)]/U', $preguri, $getarray);
        $newrouteruri = $preguri;
//    var_dump($getarray);
        if (count($getarray) > 0) {
            foreach ($getarray[0] as $getkey => $getval) {
                if (substr($getval, 0, 1) == '{') {
                    //$newrouteruri = str_replace($getval, '(.+)', $newrouteruri);
                    $newrouteruri = str_replace($getval, '{{*}}', $newrouteruri);
                }
                if (substr($getval, 0, 1) == '(') {
                    $newrouteruri = str_replace($getval, '([0-9]+)', $newrouteruri);
                }
                if (substr($getval, 0, 1) == '[') {
                    $newrouteruri = str_replace($getval, '([a-zA-Z0-9_]+)', $newrouteruri);
                }
            }
            $newrouteruri = str_replace('/', "\\/", $newrouteruri);
            $newrouteruri = str_replace('?', "\?", $newrouteruri);
            $newrouteruri = str_replace('{{*}}', "((?!\/).+)", $newrouteruri);
            preg_match_all('/' . 'startofuri-' . $newrouteruri . '-endofuri' . '/', 'startofuri-' . $uri . '-endofuri', $ifmatch);

            if (isset($ifmatch[1][0])) {
                foreach ($getarray[1] as $getkey => $getval) {
                    $_GET[$getval] = urldecode($ifmatch[1 + $getkey][0]);
                }
                if (RouterDebug) {
                    echo("<!--Dy Matched-Router: $preguri-->\r\n");
                }
                return true;
            } else {
                if (RouterDebug) {
                    echo("<!-- Not Match-Router: $preguri -->\r\n");
                }
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 设定cid和cname
     * @param $cid
     * @return bool
     */
    private function routercid($cid)
    {
        if ($cid == 0) {
            define('cid', 0);
            define('cname', '首页');
            return true;
        }
        $thichannelcache = uuu_getchannelcache($cid);
        if (!$thichannelcache || $thichannelcache['ifshow'] == 0) {
            return false;
        }
        define('cid', $cid);
        define('cname', $thichannelcache['cname']);
        return true;
    }

    /**
     * 路由执行
     * @param $routerkey
     * @return bool|void
     */
    private function route_exec($routerkey)
    {
        $router = $this->router_parsed;

//        dump_pre($router);
        if (!isset($router[$routerkey])) {
            return false;
        }


        $thisrouter = $router[$routerkey];

//        dump_pre($thisrouter['middleware']);
        //加入中间件
        if (isset($thisrouter['middleware'])) {
            if (function_exists($thisrouter['middleware'])) {
                if (!call_user_func($thisrouter['middleware'], $thisrouter)) {
                    return false;
                }
            }
        }

//        dump_pre($thisrouter);
        if (!isset($thisrouter['res'])) {
            return false;
        }
//        dump_pre();
        $ress = array();
        $res = $thisrouter['res'];
        if (is_string($res)) {
            $ress = explode(';', $res);
        } elseif (is_array($res)) {
            $ress = $res;
        } elseif (is_object($res)) {
            try {
                $ReflectionFunction = new ReflectionFunction($res);
                $parameters = $ReflectionFunction->getParameters();
            } catch (ReflectionException $e) {
                echo $e->getTraceAsString();
                return false;
            }
            $parameter_list = $this->fill_params($parameters);
            call_user_func($res, ...$parameter_list);
            return true;
        }

        foreach ($ress as $thisres) {
//            echo $thisres;
            $filefunction = explode('@', $thisres);
//            $filefunction = explode('>', $thisres);
            if (isset($filefunction[1])) {
                $thistemplatefile = $filefunction[0];
                $thistemplatefunction = $filefunction[1];
            } else {
                $thistemplatefile = $filefunction[0];
                $thistemplatefunction = false;
            }
            if (!empty($thistemplatefile) && stripos($thistemplatefile, '.') === false) {
                $thistemplatefile .= '.php';
            }
            if (empty($thistemplatefile)) {
                return false;
            }

//echo substr($thistemplatefile, 0, strlen(C) + 1);
            if (substr($thistemplatefile, 0, strlen(C) + 1) != C . "/") {
                $thistemplatefile = C . '/' . $thistemplatefile;
            }
//echo $thistemplatefile;
            //jLog("tempfile", SystemRoot . TemplateDir . DIRECTORY_SEPARATOR . 'm' . DIRECTORY_SEPARATOR . $thistemplatefile);
            $tempfile = AppDir . $thistemplatefile;
            if (!$tempfile) {
                return false;
            }

            if (file_exists($tempfile)) {
                //加载
                include_once($tempfile);
            } else {
                return false;
            }


            if ($thistemplatefunction) {
                $class_name = str_replace(".php", '', $thistemplatefile);
                if (substr($class_name, 0, strlen(C) + 1) == C . "/") {
                    $class_name = substr($class_name, strlen(C) + 1);
                }
                $class_name_arr = explode("/", $class_name);
                $class_name = '\\' . C . '\\' . join('\\', $class_name_arr);
                $method = $thistemplatefunction;

                try {
                    $parameters = (new ReflectionClass($class_name))->getMethod($method)->getParameters();
                } catch (ReflectionException $e) {
                    echo error_page_template("路由类调用错误", $e->getMessage());
                    return false;
                }
                $parameter_list = $this->fill_params($parameters);

                //提示：$_GET中的值请根据自己的程序进行过滤和验证，避免注入攻击
                try {
                    (new $class_name)->$method(...$parameter_list);//通过可变函数和可变变量，将排序好的数据传入方法执行
                } catch (Exception $e) {
                    echo error_page_template("执行路由错误", $e->getMessage());
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * @param array $parameters
     * @return array
     */
    private function fill_params(array $parameters)
    {
        $parameter_list = array();//用于存放最后根据$parameters的信息排序过的数组
        foreach ($parameters as $v) {
            if (isset($_GET[$v->name])) {
                $parameter_list[] = $_GET[$v->name];//如果能在$_GET中找到此参数，则添加到$parameter_list成员
            } elseif (isset($_POST[$v->name])) {
                $parameter_list[] = $_POST[$v->name];//如果能在$_POST中找到此参数，则添加到$parameter_list成员
            } elseif (!$v->isDefaultValueAvailable()) {
                die('missing param:' . $v->name);//否则，用isDefaultValueAvailable方法判断此参数是否有默认值，如果参数不是可选则退出程序
            } else {
                //如果参数是可选参数并且$_GET中也没有相应成员，则退出遍历。
                //因为一个参数为空后面的所有参数也必须为空，无需再遍历剩余部分，直接退出循环
                break;
            }
        }
        return $parameter_list;
    }

    public static function addGroup($domain, $grouppath, $middleware = null)
    {
        return (new routerGroup($domain, $grouppath, $middleware));
    }

    public static function addRouter($domain, $method, $uri, $handler, $cache = 3600, $access = '', $middleware = null)
    {
        self::singleton()->add_router($domain, $method, $uri, $handler, $cache, $access, $middleware);
    }

    private function add_router($domain, $method, $uri, $handler, $cache = 3600, $access = '', $middleware = null)
    {
        if (is_array($method)) {
            foreach ($method as $m) {
                $this->add_router($domain, $m, $uri, $handler, $cache, $access, $middleware);
            }
            return;
        }

        if (is_array($uri)) {
            $u_arr = $uri;
        } else {
            $u_arr = explode(';', $uri);
        }
//        dump_pre($uri);
//        dump_pre($u_arr);
        foreach ($u_arr as $u) {
            //去除右侧的/
            $u = rtrim($u, "\/\t\n\r\0\x0B");
            //如果是空的，则，设定为根路径
            if (!$u) {
                $u = '/';
            }
            $this->router_array[] = array('domain' => $domain, 'method' => $method, 'access' => $access, "cache" => $cache, 'uri' => $u, 'controller' => $handler, 'middleware' => $middleware);
        }
    }

    //开始匹配路由
    public static function addRouterWithDomain($domain, $method, $uri, $handler, $cache = 3600, $access = '', $middleware = null)
    {
        self::singleton()->add_router($domain, $method, $uri, $handler, $cache, $access, $middleware);
    }

    private function routercache($uri, $cachetime)
    {

        $routercachekey = server_name() . $uri;

        $routercache = cache_site_get($routercachekey, $cachetime, 'html');
        if ($routercache === false) {
            return false;
        } else {
            $filetime = substr(md5(cache_site_lasttime($routercachekey, 'html') . substr(SiteHash, 0, 6)), 0, 8);
            $this->routerhttpcache($cachetime, $filetime);
            return $routercache;
        }
    }

    private function routerhttpcache($cachetime, $filetime)
    {
        @header('Last-Modified: ' . $filetime);
        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
            if ($filetime == $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
                @header("HTTP/1.0 304 Not Modified");
                exit();
            }
        }
    }

    private function routersavecache($uri, $content, $cachetime)
    {
        $routercachekey = server_name() . $uri;
        cache_site_set($routercachekey, $content, $cachetime, $keykind = 'html');
        $filetime = substr(md5(cache_site_lasttime($routercachekey, 'html') . substr(SiteHash, 0, 6)), 0, 8);
        $this->routerhttpcache($cachetime, $filetime);
    }

}

class routerGroup
{
    private $rootpath;
    private $domain;
    private $middleware;

    /**
     * routerGroup constructor.
     * @param $domain
     * @param $rootpath
     * @param null $middleware
     */
    public function __construct($domain, $rootpath, $middleware = null)
    {
        $rootpath = rtrim($rootpath, "\/\t\n\r\0\x0B");
        $this->rootpath = $rootpath;
        $this->domain = $domain;
        $this->middleware = $middleware;
    }

    /**
     * @param $method
     * @param $uri
     * @param $handler
     * @param int $cache
     * @param string $access
     * @param bool|string $middleware 设定为true则应用组创建的中间件，也可传入新的中间件函数名或设置为false取消应用中间件
     */
    public function add_router($method, $uri, $handler, $cache = 3600, $access = '', $middleware = true)
    {
        if (is_array($uri)) {
            $u_arr = $uri;
        } else {
            $u_arr = explode(';', $uri);
        }

        if (!$middleware) {
            $middleware = null;
        }

        if ($middleware && !function_exists($middleware)) {
            $middleware = $this->middleware;
        }

        foreach ($u_arr as $u) {
            //去除首尾的空白和/
            $u = trim($u, "\/\t\n\r\0\x0B");
            router::addRouterWithDomain($this->domain, $method, $this->rootpath . '/' . $u, $handler, $cache, $access, $middleware);
        }
    }
}